var fs=require("fs");

/**
 * Class: LNModelGenerator
 * Properties:
 *		sql typeof LNSql								- Connection handler
 *		configname typeof String						- Filename of config file (i.e. ..../config.js)
 *		sqldir typeof String							- Path to write SQL files (i.e. code/sql/)
 *		ssjsdir typeof String							- Path to write server-side JavaScript (i.e. code/)
 *		config array of LNModelConfig
 */

/**
 * Class: LNModelConfig
 * Properties:
 * 		name typeof String							- model name
 *		table typeof String							- table name
 *		folder typeof String						- folder name
 *		columns array of LNModelConfigColumn		- source information about columns
 *		columns_l10n array of LNModelConfigColumn	- source information about columns
 *		patches typeof LNModelConfigPatches		- patches
 *		has_ordering typeof Boolean					- has ordering column or not
 *		has_hier typeof Boolean						- has hierarchy (parent_id column) or not
 *		adminpanel typeof LNModelConfigAdminpanel	- Autogenerated adminpanel settings
 *		name_column typeof String					- "Name" column, i.e. "main.name" or "main.id" or "coalesce(mainl1.name,mainl2.name)"
 *		code_column typeof String					- "Code" column, i.e. "main.code"
 */

/**
 * Class: LNModelConfigColumn
 * Properties:
 *		name typeof String							- column name
 *		not_null typeof Boolean						- is column not null?
 *		datatype typeof String						- varchar/integer/....
 *		table typeof String							- foreign key table
 *		skip_insupd typeof Boolean					- skip insert/update
 *		skip_edit typeof Boolean					- skip edit
 *		skip_select typeof Boolean					- skip select
 */

/**
 * Class: LNModelConfigPatches
 * Properties:
 *		columns Object of LNModelConfigColumnPatch			- Column patches
 *		lists array of LNModelConfigListInfo					- Information for SQL generator
 *		get typeof LNModelConfigSelectParams					- Info
 *		get_extra_locale typeof LNModelConfigSelectParams		- Info
 *		extra_fields array of LNModelConfigExtraField			- Array of extra fields
 *		extra_fields_l10n array of LNModelConfigExtraField	- Array of extra fields
 */

/**
 * Class: LNModelConfigExtraField
 * Properties:
 *		name typeof String							- name
 *		datatype typeof String						- datatype
 */
/**
 * Class: LNModelConfigAdminpanel
 * Properties:
 *		enabled typeof Boolean						- enabled
 *		role typeof String							- role code (Troles.code)
 *		section typeof String						- section of admin panel navigation
 *		label typeof String							- label of admin panel navigation
 *		list_ordering								- TODO
 *		edit_ordering								- TODO
 */

/**
 * Class: LNModelConfigSelectParams
 * Properties:
 */

/**
 * Class: LNModelConfigColumnPatch
 * Properties:
 *		datatype typeof String						- varchar/integer/boolean/...
 */

/**
 * Class: LNModelConfigListInfo
 * Properties:
 *		name typeof String								- name
 *		order_by typeof String							- order_by part of SQL query (i.e. "main.id")
 *		skip typeof Object								- names of columns to skip fetching
 *		where typeof String								- SQL query part
 *		config typeof LNModelConfigSelectParams		- Info for build_sql_select_part_column
 *		
 */


/**
 * Constructor: LNModelGenerator
 */
function LNModelGenerator(sql,views)
{
	this.sql=sql;
	this.views=views;
}

/**
 * Method: init
 */
LNModelGenerator.prototype.init=function(configname,sqldir,ssjsdir)
{
	this.configname=configname;
	this.sqldir=sqldir;
	this.ssjsdir=ssjsdir;
	this.load_config();
}

/**
 * Method: add_parent
 */
LNModelGenerator.prototype.add_parent=function(configname,sqldir,ssjsdir)
{
	if (this.parent) {
		this.parent.add_parent(configname,sqldir,ssjsdir);
		return;
	}
	this.init_parent(configname,sqldir,ssjsdir);
}
/**
 * Method: init_parent
 */
LNModelGenerator.prototype.init_parent=function(configname,sqldir,ssjsdir)
{
	this.parent=new LNModelGenerator();
	this.parent.init(configname,sqldir,ssjsdir);
}

/**
 * Method: work
 */
LNModelGenerator.prototype.work=function(configname,codedir)
{
}

/**
 * Method: load_config
 */
LNModelGenerator.prototype.load_config=function()
{
	if ((new fs.File(this.configname)).exists()) {
		this.config=require(this.configname).config;
		for (var i=0;i<this.config.length;i++) {
			var tinfo=this.config[i];
			for (var j=0;j<tinfo.patches.lists.length;j++) {
				var list=tinfo.patches.lists[j];
				if (!list.variant) list.variant=list.is_count?"count":"select";
			}
		}
	} else {
		this.config=[
		];
	}
}

/**
 * Method: sort
 */
LNModelGenerator.prototype.sort=function()
{
	this.config.sort(function(a,b) {
		if (a.name<b.name) return -1;
		if (a.name>b.name) return 1;
		return 0;
	});
}

/**
 * Method: save_config
 */
LNModelGenerator.prototype.save_config=function()
{
	if (this.dirty) throw "Config file can be dirty!";
	this.sort();
	//throw this.configname;
	
	var f=new fs.File(this.configname);
	
	for (var i=0;i<this.config.length;i++) {
		var tinfo=this.config[i];
		for (var j=0;j<tinfo.patches.lists.length;j++) {
			var list=tinfo.patches.lists[j];
			delete list.is_count;
			delete list.preview;
		}
	}
	
	var flag;
	var s="exports.config="+JSON.stringify(this.config,undefined,"\t");
	if (!f.exists()) {
		flag=true;
	} else {
		f.open("r");
		if (s!=f.read().toString("utf-8")) flag=true;
		f.close();
	}
	if (!flag) return;
	f.open("w");
	f.write(s);
	f.close();
}

/**
 * Method: load_table
 */
LNModelGenerator.prototype.load_table=function(tname)
{

}

/**
 * Method: parse_all
 *
 * Returns:
 *		typeof String - Error texts or null
 */
LNModelGenerator.prototype.parse_all=function()
{
	var r;
	for (var i=0,s=this.config.length;i<s;i++) {
		var rr=this.parse(this.config[i]);
		if (rr) {
			if (!r) r=""; else r+="\n";
			r+=rr;
		}
	}
	return r;
}


/**
 * Method: db_fetch_columns
 *
 * Returns:
 *		
 */
LNModelGenerator.prototype.db_fetch_columns=function(tablename,is_l10n)
{
	var t=this;
	var oid=this.sql.execute_and_fetch_one("database_info/pg/get",{table:tablename.toLowerCase()});
	if (!oid) return null;
	oid=oid.oid;
	var cols=[];
	var cols_by_name={};
	this.sql.execute_and_fetch("database_info/pg/columns",{id:oid}).forEach(function(c) {
		if (c.name.match(/^(id)$/)) return;
		if (is_l10n && c.name.match(/^(id|fid_id|l10n_id)$/)) return;
		var col={
			name:c.name,
			not_null:c.not_null
		};
		if (c.name.match(/^(date_cr|date_mo)$/)) col.skip_edit=col.skip_insupd=1;
		if (c.name.match(/^calc_/)) col.skip_edit=1;
		if (c.name.match(/^(user_cr_id)$/)) col.skip_edit=1;
		if (c.name.match(/^(ordering)$/)) col.skip_edit=col.skip_admin_list=col.skip_insupd=1;
		if (c.default_value && !(c.default_value.match(/^nextval|now\(\)/)) ) col.default_value=c.default_value;
		switch (c.datatype) {
			case "timestamp without time zone":
				col.datatype="timestamp";
				break;
			case "tsvector":
				col.skip_select=col.skip_edit=col.skip_insupd=1;
				break;
			case "character varying":
				col.datatype="varchar";
				col.maxsize=c.datatype_full.match(/\((\d+)\)/)[1];
				break;
			default:
				col.datatype=c.datatype;
		}
		if (c.name=="enabled") col.datatype="boolean";
		cols.push(col);
		cols_by_name[col.name]=col;
	});

	var fkid=1;
	this.sql.execute_and_fetch("database_info/pg/foreign_keys",{id:oid}).forEach(function(fk) {
		var arr=fk.condef.match(/FOREIGN KEY \((\w+)\) REFERENCES (\w+)\(id\)/);
		if (!arr) throw "LNModelGenerator.prototype.get_db_version - could not parse fk definition - "+fk.condef;
		var name=arr[1];
		if (name.match(/^(id)$/)) return;
		if (is_l10n && name.match(/^(id|fid_id|l10n_id)$/)) return;
		if (!cols_by_name[arr[1]]) throw "LNModelGenerator.prototype.get_db_version - No column found in local table (can not be?!) - "+fk.condef;
		cols_by_name[name].datatype="fk";
		var fm=t.find_by_table(arr[2],true);
		cols_by_name[name].table=fm?fm.table:arr[2];
		var _fkid="__"+name.replace(/_id$/,"");
		cols_by_name[name].fkid="t"+(is_l10n?"l":"")+_fkid;
		if (arr[2]=="tfiles") cols_by_name[name].datatype="file";
		if (arr[2]=="tfile_folders") cols_by_name[name].datatype="file_folder";
		fkid++;
	});
	return cols;
}

/**
 * Method: get_db_version
 *
 * Returns:
 *		typeof Object - columns and foreign keys
 */
LNModelGenerator.prototype.get_db_version=function(obj)
{
	var t=this;
	var cols=this.db_fetch_columns(obj.table,0);
	var cols_l10n=this.db_fetch_columns(obj.table+"_l10n",1);
	return {
		table:obj.table,
		columns:cols,
		columns_l10n: cols_l10n?cols_l10n:[]
	};
}

/**
 * Method: guess_code_column
 *
 * Returns:
 *		typeof String - 
 */
LNModelGenerator.prototype.guess_code_column=function(mconfig)
{
	for (var i=0,s=mconfig.columns.length;i<s;i++) {
		var col=mconfig.columns[i];
		if (col.name=="code") return "main."+col.name;
	}
	return undefined;
}

/**
 * Method: guess_name_column
 *
 * Returns:
 *		typeof String - 
 */
LNModelGenerator.prototype.guess_name_column=function(mconfig)
{

		if (mconfig.name=="User") return "main.calc_full_name";
		var name_column;//="main.id";
		if (!mconfig.columns) throw "Can't find columns for " + JSON.stringify(mconfig);
		for (var i=0,s=mconfig.columns.length;i<s;i++) {
			var col=mconfig.columns[i];
			if (!name_column && col.name.match(/^(name|calc_name)$/)) name_column="main."+col.name;
		}
		if (mconfig.columns_l10n.length) {
			for (var i=0,s=mconfig.columns_l10n.length;i<s;i++) {
				var col=mconfig.columns_l10n[i];
				if (!name_column && col.name.match(/^(name|calc_name)$/)) name_column="coalesce(mainl1."+col.name+",mainl2."+col.name+")";
			}
		}
		return name_column;

}

/**
 * Method: parse
 *
 * Returns:
 *		typeof String - Error text or null
 */
LNModelGenerator.prototype.parse=function(mconfig)
{
	var err;
	var dbv=this.get_db_version(mconfig);
	mconfig.columns=dbv.columns;
	//if (!mconfig.columns) return "Не могу найти колонок для модельки "+mconfig.name+", может быть таблица "+mconfig.table+" удалена?";
	mconfig.columns_l10n=dbv.columns_l10n;
	mconfig.name_column=this.guess_name_column(mconfig);
	mconfig.code_column=this.guess_code_column(mconfig);
	var has_enabled;
	for (var i=0;i<mconfig.columns.length;i++) {
		if (mconfig.columns[i].name=="ordering") mconfig.has_ordering=true;
		if (mconfig.columns[i].name=="parent_id") mconfig.has_hier=true;
		if (mconfig.columns[i].name=="enabled") has_enabled=true;
	}
	if (!mconfig.patches) mconfig.patches={};
	if (!mconfig.patches.columns) mconfig.patches.columns={};

	if (!mconfig.patches.lists) mconfig.patches.lists=[];
	function list_push(list)
	{
		if (!list.variant) list.variant="select";
		for (var i=0;i<mconfig.patches.lists.length;i++) {
			if (mconfig.patches.lists[i].name==list.name) return;
		}
		mconfig.patches.lists.push(list);
	}
	list_push({name:"list",skip:{},where:"",order_by:(mconfig.has_ordering?"main.ordering":"main.id")});
	list_push({name:"count",skip:{},where:"",variant:"count"});
	if (mconfig.has_hier) {
		list_push({name:"list_level",skip:{},where:"coalesce(main.parent_id,0)=coalesce(:parent_id,0)",order_by:(mconfig.has_ordering?"main.ordering":"main.id")});	
		list_push({name:"with_recurse",skip:{},where:"",start_with:"",connect_by:"",order_by:(mconfig.has_ordering?"init.ordering":"init.id"),variant:"recurse"});	
		list_push({name:"count_level",skip:{},where:"coalesce(main.parent_id,0)=coalesce(:parent_id,0)",order_by:"",variant:"count"});
		if (has_enabled) {
			list_push({name:"list_level_enabled",skip:{},where:"coalesce(main.parent_id,0)=coalesce(:parent_id,0) and\nmain.enabled=1",order_by:(mconfig.has_ordering?"main.ordering":"main.id")});
			list_push({name:"count_level_enabled",skip:{},where:"coalesce(main.parent_id,0)=coalesce(:parent_id,0) and\nmain.enabled=1",order_by:"",variant:"count"});
			list_push({name:"with_recurse_enabled",skip:{},where:"",start_with:"init.enabled=1",connect_by:"init.enabled=1",order_by:(mconfig.has_ordering?"init.ordering":"init.id"),variant:"recurse"});
		}
	}
	if (has_enabled) {
		list_push({name:"list_enabled",skip:{},where:"main.enabled=1",order_by:(mconfig.has_ordering?"main.ordering":"main.id")});
		list_push({name:"count_enabled",skip:{},where:"main.enabled=1",variant:"count"});
	}
	if (!mconfig.adminpanel) mconfig.adminpanel={};
	return err;
}

/**
 * Method: find_by_table
 */
LNModelGenerator.prototype.find_by_table=function(name,lookup_parent)
{
	for (var i=0,s=this.config.length;i<s;i++) {
		if (this.config[i].table.toLowerCase()==name.toLowerCase()) return this.config[i];
	}
	if (lookup_parent && this.parent) return this.parent.find_by_table(name);
	return null;
}

/**
 * Method: del
 */
LNModelGenerator.prototype.del=function(name)
{
	var i=0;
	while (i<this.config.length) {
		if (this.config[i].name==name) this.config.splice(i,1); else i++;
	}
}

/**
 * Method: find
 */
LNModelGenerator.prototype.find=function(name)
{
	for (var i=0,s=this.config.length;i<s;i++) {
		if (this.config[i].name==name) return this.config[i];
	}
	return null;
}

/**
 * Method: add
 */
LNModelGenerator.prototype.add=function(mconfig)
{
	if (this.find(mconfig.name)) throw "Model exists!";
	this.config.push(mconfig);
	mconfig.patches={};
	return mconfig;
}

/**
 * Method: build_all
 */
LNModelGenerator.prototype.build_all=function()
{
	var ret;
	this.big_files={
		controllers:"",
		modeldefs:""
	};
	for (var i=0;i<this.config.length;i++) {
		var rr=this.build(this.config[i]);
		if (rr) {
			if (r) r+="\n"; else r="";
			rr+=r;
		}
	}
	this.update_file(this.ssjsdir+"/models/_auto.js","exports.add=[\n	"+this.big_files.modeldefs+"];\n","code/models/_auto.js");
	this.update_file(this.ssjsdir+"/controllers/_auto.js","exports.add=[\n"+this.big_files.controllers+"];\n","code/models/_auto.js");
	return ret;
}

/**
 * Method: build
 */
LNModelGenerator.prototype.build=function(mconfig)
{
	this.dirty=1;
	mconfig.tmp_columns			=mconfig.columns;
	mconfig.tmp_columns_l10n	=mconfig.columns_l10n;
	mconfig.columns				=this.gen_patched_columns(mconfig,"columns");
	mconfig.columns_l10n		=this.gen_patched_columns(mconfig,"columns_l10n");

	this.build_sql_files(mconfig);
	if (this.big_files.modeldefs.length) this.big_files.modeldefs+=",\n	";
	this.big_files.modeldefs+=this.build_modeldef(mconfig);

	if (mconfig.adminpanel.enabled && mconfig.adminpanel.enabled=="1") {
		this.build_admin_views(mconfig);
		if (this.big_files.controllers.length) this.big_files.controllers+=",\n";
		this.big_files.controllers+=this.build_admin_controller(mconfig);
	}

	mconfig.columns				=mconfig.tmp_columns;
	mconfig.columns_l10n		=mconfig.tmp_columns_l10n;
	delete mconfig.tmp_columns;
	delete mconfig.tmp_columns_l10n;
}

/**
 * Method: update_file
 */
LNModelGenerator.prototype.update_file=function(filename,text,shortfilename)
{
	if (!filename.match(/\.html$/)) text="/* AUTOGENERATED. DO NOT EDIT ("+shortfilename+") */\n"+text; else text="<!-- AUTOGENERATED. DO NOT EDIT -->\n"+text;
	var p=filename;
	var p1="";
	var arr;
//	var debug=[];
	while (arr=p.match(/^(.+?)\//)) {
		p1+=arr[1];
		p=p.substr(arr[1].length);
		var d=new fs.Directory(p1);
		if (!d.exists()) {
			try {
				d.create();
			} catch (e) {
				throw "Cannot create directory "+p1;
			}
		}
//		debug.push(p1);
	}
//	throw debug;
	var f=new fs.File(filename);
	if (f.exists()) {
		f.open("r");
		var contents=f.read();
		var r=contents.toString("utf-8");
		f.close();
		if (r==text) return;
	}
	try {
		f.open("w");
		f.write(text);
		f.close();
	} catch(e) {
		throw new Error("Could not save file "+f.toString()+", error="+e);
	}
}

/**
 * Method: gen_patched_columns
 */
LNModelGenerator.prototype.gen_patched_columns=function(mconfig,ctname)
{
	var t=this;
	var ret=[];
	mconfig[ctname].forEach(function(corig) {
		var c={};
		for (var k in corig) c["_orig_"+k]=c[k]=corig[k];
		ret.push(c);
		if (!mconfig.patches[ctname]) return;
		var hash=mconfig.patches[ctname][corig.name];
		if (!hash) return;
		for (var k in hash) c["_changed_"+k]=c[k]=hash[k];
	});
	return ret;
}

/**
 * Method: set_column_patch_parameter
 */
LNModelGenerator.prototype.set_column_patch_parameter=function(mconfig,ctname,column,field,value)
{
	var orig;
	var arr=mconfig[ctname];
	for (var i=0;i<arr.length;i++) if (arr[i].name==column) orig=arr[i][field];
	if (orig==value || value=="") {
		if (!mconfig.patches[ctname]) return;
		if (!mconfig.patches[ctname][column]) return;
		delete mconfig.patches[ctname][column][field];
	} else {
		if (!mconfig.patches[ctname]) mconfig.patches[ctname]={};
		if (!mconfig.patches[ctname][column]) mconfig.patches[ctname][column]={};
		mconfig.patches[ctname][column][field]=value;
	}
}


/**
 * Method: build_sql_select_part_column
 */
LNModelGenerator.prototype.build_sql_select_part_column=function(mconfig,params,talias,talias2,c)
{
	var sel="";
	var from="";
	var cname=talias2?"coalesce("+talias+"."+c.name+","+""+talias2+"."+c.name+")":talias+"."+c.name;
	if (c.skip_select) return {sel:sel,from:from};
	switch (c.datatype) {
		case "timestamp":
			sel+="	to_char("+cname+",'DD.MM.YYYY HH24:MI:SS') as "+c.name;
			break;
		case "date":
			sel+="	to_char("+cname+",'DD.MM.YYYY') as "+c.name;
			break;
		case "fk":
		case "file_folder":
			sel+="	"+cname+" as "+c.name;
			var mconfig2=this.find_by_table(c.table,true);
			if (!mconfig2) break;
			if (!mconfig2.name_column && !mconfig2.code_column) break;
			from+="\n	left join "+c.table+" "+c.fkid+" on "+cname+"="+c.fkid+".id";
			if (mconfig2.name_column) {
				if (mconfig2.name_column.match(/^main\./)) {
				} else {
					from+="\n	left join "+c.table+"_l10n "+c.fkid+"l1 on "+c.fkid+".id="+c.fkid+"l1.fid_id and "+c.fkid+"l1.l10n_id=:use_l10n_id";
					from+="\n	left join "+c.table+"_l10n "+c.fkid+"l2 on "+c.fkid+".id="+c.fkid+"l2.fid_id and "+c.fkid+"l2.l10n_id=:fallback_l10n_id";
				}
				sel+=",\n	"+mconfig2.name_column.replace(/main(|l[12])\./g,function (p,p1) { return c.fkid+p1+".";})+" as "+c.name.replace(/_id$/,"_name");
			}
			if (mconfig2.code_column) {
				sel+=",\n	"+mconfig2.code_column.replace(/main(|l[12])\./g,function (p,p1) { return c.fkid+p1+".";})+" as "+c.name.replace(/_id$/,"_code");
			}
			break;
		case "file":
			var cname_short=c.name.replace(/_id$/,"");
			sel+="	"+cname+" as "+c.name;
			sel+=",\n	"+c.fkid+".ext as "+cname_short+"_ext";
			sel+=",\n	"+c.fkid+".file_folder_id as "+cname_short+"_file_folder_id";
			from+="\n	left join "+c.table+" "+c.fkid+" on "+cname+"="+c.fkid+".id";
			break;
		default:
			sel+="	"+cname+" as "+c.name;
			break;
	}
	if (sel.length) sel=",\n"+sel;
	return {sel:sel,from:from};
}

/**
 * Method: build_sql_select
 *
 */
LNModelGenerator.prototype.build_sql_select=function(mconfig,list)
{
	if (!list.config) list.config={};
	// GET:		this.build_sql_select_part(mconfig,mconfig.patches.get,{})+"\nwhere\n	main.id=:id";
	// LIST FK:	this.build_sql_select_part(mconfig,{},{list_fk:1,has_ordering:mconfig.has_ordering})

	var where="";
	if (list.where) where="\nwhere\n	"+this.recode_col_names(mconfig,list.where).replace(/\n/g,"\n\t");
	var parts=this.build_sql_select_part(mconfig,list.config,{variant:list.variant,list:list});


	if (list.variant=="list_fk" || list.variant=="list_fk_with_recurse") {
		if (mconfig.name_column) {
			parts.select="	main.id,\n	"+mconfig.name_column+" as name";
		} else if (mconfig.columns.some(function(c){ return c.name == "val_s"; })) {
			parts.select="	main.id,\n	'ID#' || main.id || ' - ' || main.val_s as name";
		} else {
			parts.select="	main.id,\n	'ID#' || main.id as name";
		}
		if (mconfig.has_ordering) list.order_by="main.ordering"; else list.order_by=(mconfig.name_column || 'main.id');
	}
	switch (list.variant || "select") {
		case "select":
			return "select\n"+parts.select+"\nfrom\n"+parts.from+where+"\n"+(list.order_by?"order by "+this.recode_col_names(mconfig,list.order_by):"");
			break;
		case "count":
			return "select count(*) as cnt\nfrom\n"+parts.from+where;
			break;
		case "get":
			return "select\n"+parts.select+"\nfrom\n"+parts.from+"\nwhere\n	main.id=:id";
			break;
		case "list_fk":
			var tail="";
			if (mconfig.has_hier) tail="\nwhere\n	coalesce(main.parent_id,0)=coalesce(:parent_id,0)";
			return "select\n"+parts.select+"\nfrom\n"+parts.from+tail+"\norder by "+list.order_by;
			break;
		case "with":
			throw "TODO";
			break;
		case "recurse":
		case "list_fk_with_recurse":
			parts.select+=",\n	main.level";
			var init="";
			init+="with recursive recurse_query as (\n";
			init+="		select init.*,1 as level,array["+list.order_by.replace(/main\./,"init.")+"] as path\n";
			init+="		from "+mconfig.table+" init\n";
			init+="		where\n";
			init+="			coalesce(init.parent_id,0)=coalesce(:parent_id,0)";
			if (list.start_with) init+=" and\n"+list.start_with.replace(/^/g,"\t\t\t");
			init+="\n";
			init+="	union all\n";
			init+="		select init.*,rq.level+1,rq.path || "+list.order_by.replace(/main\./,"init.")+"\n";
			init+="		from recurse_query rq,"+mconfig.table+" init\n";
			init+="		where\n";
			init+="			init.parent_id=rq.id";
			if (list.connect_by) init+=" and\n"+list.connect_by.replace(/^/g,"\t\t\t");
			init+="\n";
			init+=")\n";
			return init+"select\n"+parts.select+"\nfrom\n"+parts.from+where+"\norder by path";
			break;
		default:
			throw "UNKNOWN variant "+list.variant;
	}
}

/**
 * Method: build_sql_select_part
 *		Return "select ... " query, following variants:
 *
 *
 * Parameters:
 *		build_params typeof Object				- configuration
 *		build_params.variant typeof String		- "select", "count", "with", "recurse"
 *		build_params.list_fk typeof Boolean		-
 *		build_params.list typeof Object			-
 */
LNModelGenerator.prototype.build_sql_select_part=function(mconfig,params,build_params)
{
	if (!params) params={};
	var listinfo=build_params.list || {};
	if (!listinfo.skip) listinfo.skip={};
	if (!listinfo.add) listinfo.add=[];
	if (!listinfo.leftjoins) listinfo.leftjoins=[];
//	throw {listinfo:listinfo};
	var sel="	main.id";
	var from="	"+((build_params.variant=="recurse" || build_params.variant=="list_fk_with_recurse")?"recurse_query":mconfig.table)+" main";
	for (var i=0,s=mconfig.columns.length;i<s;i++) {
		var col=mconfig.columns[i];
		if (listinfo.skip[col.name]) continue;
		var r=this.build_sql_select_part_column(mconfig,params,"main",null,col);
		sel+=r.sel;
		from+=r.from;
	}
	if (mconfig.columns_l10n.length) {
		sel+=",\n	coalesce(mainl1.l10n_id,mainl2.l10n_id) as l10n_id";
		from+="\n	left join "+mconfig.table+"_l10n mainl1 on main.id=mainl1.fid_id and mainl1.l10n_id=:use_l10n_id";
		from+="\n	left join "+mconfig.table+"_l10n mainl2 on main.id=mainl2.fid_id and mainl2.l10n_id=:fallback_l10n_id";
		for (var i=0,s=mconfig.columns_l10n.length;i<s;i++) {
			var col=mconfig.columns_l10n[i];
			if (listinfo.skip[col.name]) continue;
			var r=this.build_sql_select_part_column(mconfig,params,"mainl1","mainl2",col);
			sel+=r.sel;
			from+=r.from;
		}
	}
	for (var i=0;i<listinfo.add.length;i++) {
		if (listinfo.add[i].match(/^\s*$/)) continue;
		sel+=",\n	"+this.recode_col_names(mconfig,listinfo.add[i]);
	}
	for (var i=0;i<listinfo.leftjoins.length;i++) {
		if (listinfo.leftjoins[i].match(/^\s*$/)) continue;
		from+="\n	left join "+this.recode_col_names(mconfig,listinfo.leftjoins[i]);
	}
//	if (params.where) throw params;
//	if (mconfig.name=="MarketManufacturer") throw {mconfig:mconfig,sel:sel,from:from};
//	if (build_params.variant=="count") sel="count(*) as cnt";
	var tail="";
/*	if (build_params.list_fk) {
		if (mconfig.name_column)
			sel="	main.id,\n	"+mconfig.name_column+" as name";
		else if (mconfig.columns.some(function(c){ return c.name == "val_s"; }))
			sel="	main.id,\n	'ID#' || main.id || ' - ' || main.val_s as name";
		else
			sel="	main.id,\n	'ID#' || main.id as name";

		if (mconfig.has_hier) tail+="\nwhere\n	coalesce(main.parent_id,0)=coalesce(:parent_id,0)";
		if (mconfig.has_ordering) tail+="\norder by main.ordering"; else tail+="\norder by "+ (mconfig.name_column || 'main.id');
	}*/
	return {select:sel,from:from,tail:tail};
/*	if (build_params.variant=="select") return "select\n"+sel+"\nfrom\n"+from+tail;
	if (build_params.variant=="count") return "select\n"+sel+"\nfrom\n"+from+tail;
	if (build_params.variant=="with") throw "TODO";
	if (build_params.variant=="recurse") {
		var wr="with recursive recurse_query as (\n";
		wr+="		select init.*,1 as level,array[ff.ordering] as path
		from Tfile_folders ff
		where ff.parent_id is null
	union all
		select ff.*,rq.level+1,rq.path || ff.ordering
		from recurse_query rq,Tfile_folders ff
		where ff.parent_id=rq.id
)
";
		return ""
	}*/

}


/**
 * Method: build_sql_select_extra_part
 */
LNModelGenerator.prototype.build_sql_select_extra_part=function(mconfig,params,all)
{
	if (!params) params={};
	var sel="	mainl1.id";
	var from="";
	if (all) {
		sel+=",\n	l10n.id as l10n_id";
		from+="	Tl10n l10n\n";
		from+="	left join "+mconfig.table+"_l10n mainl1 on mainl1.fid_id=:id and mainl1.l10n_id=l10n.id";
	} else {
		from+="	"+mconfig.table+" main\n";
		from+="	left join "+mconfig.table+"_l10n mainl1 on main.id=mainl1.fid_id and mainl1.l10n_id=:use_l10n_id";
	}
	for (var i=0,s=mconfig.columns_l10n.length;i<s;i++) {
		var r=this.build_sql_select_part_column(mconfig,params,"mainl1",null,mconfig.columns_l10n[i]);
		sel+=r.sel;
		from+=r.from;
	}
	return "select\n"+sel+"\nfrom\n"+from;
}

/**
 * Method: build_sql_insupd
 *
 * Parameters:
 *		obj typeof				- Object describing a model
 *		mode typeof String		- "insert" or "update"
 *		is_l10n typeof Boolean	- generating l10n or main file
 */
LNModelGenerator.prototype.build_sql_insupd=function(mconfig,mode,is_l10n)
{
	var r1="",r2="",r3="";
	if (mode=="insert") {
		r1="insert into "+mconfig.table+(is_l10n?"_l10n":"")+" (";
		r2=")\n	values (";
		r3=")";
		if (is_l10n) {r1+="fid_id,l10n_id,";r2+=":fid_id,:l10n_id,";}
	} else {
		r1="update "+mconfig.table+(is_l10n?"_l10n":"")+"\nset";
		if (is_l10n) r2="\nwhere fid_id=:fid_id and l10n_id=:l10n_id"; else r2="\nwhere id=:id";
		r3="";
	}
	var arr=is_l10n?mconfig.columns_l10n:mconfig.columns;
	var j=0;
	for (var i=0;i<arr.length;i++) {
		var c=arr[i];
		if (c.skip_insupd) {
			if (c.name=="date_mo" && mode=="update") {
				if (j) r1+=",";
				r1+="\n	";r1+=c.name+"=now()";
				j++;
			}
			continue;
		}
		var t1=c.name,t2;
		switch (c.datatype) {
			case "date":
				t2="to_date(:"+c.name+",'DD.MM.YYYY')";
				break;
			case "timestamp":
				t2="to_timestamp(:"+c.name+",'DD.MM.YYYY HH24:MI:SS')";
				break;
			default:
				t2=":"+c.name;
		}
		if (mode=="insert") {
			if (j) {r1+=",";r2+=",";}
			r1+=t1;r2+=t2;
		} else {
			if (j) r1+=",";
			r1+="\n	";r1+=t1;r1+="=";r1+=t2;
		}
		j++;
	}
	return r1+r2+r3;
}

/**
 * Method: recode_col_names
 */
LNModelGenerator.prototype.recode_col_names=function(mconfig,sql)
{
	function bld(cols)
	{
		for (var i=0;i<cols.length;i++) {
			if (!cols[i].fkid) continue;
			sql=sql.replace("<"+cols[i].name+">.",cols[i].fkid+".");
		}
	}
	bld(mconfig.columns);
	return sql;
}

/**
 * Method: build_sql_files
 */
LNModelGenerator.prototype.build_sql_files=function(mconfig)
{
	var path=this.sqldir+"_models/"+mconfig.folder+"/";
	var shortpath="code/sql/_models/"+mconfig.folder+"/";
	// DEFAULT
	this.update_file(path+"default.sql",this.build_sql_select(mconfig,{variant:"select",name:"default",skip:{},where:"",order_by:""}),shortpath+"get.sql");
	// GET
	this.update_file(path+"get.sql",this.build_sql_select(mconfig,{variant:"get"}),shortpath+"get.sql");
	// GET EXTRA LOCALE
	if (mconfig.columns_l10n.length) this.update_file(path+"get_extra_locale.sql",this.build_sql_select_extra_part(mconfig,mconfig.patches.get_extra_locale,0)+"\nwhere\n	main.id=:id",shortpath+"get_extra_locale.sql");
	// GET ALL LOCALES
	if (mconfig.columns_l10n.length) this.update_file(path+"get_all_locales.sql",this.build_sql_select_extra_part(mconfig,mconfig.patches.get_extra_locale,1),shortpath+"get_all_locales.sql");
	// LISTs
	for (var i=0;i<mconfig.patches.lists.length;i++) {
		var list=mconfig.patches.lists[i];
		this.update_file(path+list.name+".sql",this.build_sql_select(mconfig,list),shortpath+list.name+".sql");
	}
	// LIST FK
	this.update_file(path+"list_fk.sql",this.build_sql_select(mconfig,{variant:"list_fk"}),shortpath+"list_fk.sql");
	// LIST FK
	if (mconfig.has_hier) {
		this.update_file(path+"list_fk_with_recurse.sql",this.build_sql_select(mconfig,{variant:"list_fk_with_recurse"}),shortpath+"list_fk_with_recurse.sql");
	}
	

	// INSERT
	this.update_file(path+"insert.sql",this.build_sql_insupd(mconfig,"insert",0),shortpath+"insert.sql");
	// INSERT LOCALE
	if (mconfig.columns_l10n.length) this.update_file(path+"insert_locale.sql",this.build_sql_insupd(mconfig,"insert",1),shortpath+"insert_locale.sql");
	// EXISTS LOCALE
	if (mconfig.columns_l10n.length) this.update_file(path+"exists_locale.sql","select 1 from "+mconfig.table+"_l10n where fid_id=:fid_id and l10n_id=:l10n_id",shortpath+"exists_locale.sql");
	// UPDATE
	this.update_file(path+"update.sql",this.build_sql_insupd(mconfig,"update",0),shortpath+"update.sql");
	// UPDATE LOCALE
	if (mconfig.columns_l10n.length) this.update_file(path+"update_locale.sql",this.build_sql_insupd(mconfig,"update",1),shortpath+"update_locale.sql");
	// DELETE
	this.update_file(path+"delete.sql","delete from "+mconfig.table+" where id=:id",shortpath+"delete.sql");
	// DELETE LOCALE
	if (mconfig.columns_l10n.length) this.update_file(path+"delete_l10n.sql","delete from "+mconfig.table+"_l10n where fid_id=:id",shortpath+"delete_l10n.sql");

	// GET ORDERING
	if (mconfig.has_ordering) this.update_file(path+"get_ordering.sql","select ordering from "+mconfig.table+" where id=:id",shortpath+"get_ordering.sql");
	// SET ORDERING
	if (mconfig.has_ordering) this.update_file(path+"set_ordering.sql","update "+mconfig.table+" set ordering=:ordering where id=:id",shortpath+"set_ordering.sql");
}

/**
 * Method: build_modeldef
 */
LNModelGenerator.prototype.build_modeldef=function(mconfig)
{
	var t=this;
	var h={
		_type:"model",
		_config: {
			name: mconfig.name,
			table: mconfig.table,
			folder: mconfig.folder,
			has_ordering: mconfig.has_ordering,
			has_hier: mconfig.has_hier,
			has_l10n: mconfig.columns_l10n.length?1:0
		},
		methods: {}
	};
	function fill_fields(arr,arr2,suffix)
	{
		h._config["field_names"+suffix]=[];
		h._config["field_datatypes"+suffix]=[];
		h._config["field_files"+suffix]=[];
		h._config["field_fkmodels"+suffix]=[];
		for (var i=0;i<arr.length;i++) {
			var c=arr[i];
			h._config["field_names"+suffix].push(c.name);
			h._config["field_datatypes"+suffix].push(c.datatype);
			h._config["field_fkmodels"+suffix].push("");
			switch (c.datatype) {
				case "file":
					h._config["field_files"+suffix].push(c.name.replace(/_id$/,""));
					h._config["field_names"+suffix].push(c.name.replace(/_id$/,"_ext"));
					h._config["field_names"+suffix].push(c.name.replace(/_id$/,"_file_folder_id"));
					h._config["field_names"+suffix].push(c.name.replace(/_id$/,"_url"));
					h._config["field_datatypes"+suffix].push("");
					h._config["field_datatypes"+suffix].push("");
					h._config["field_datatypes"+suffix].push("");
					h._config["field_fkmodels"+suffix].push("");
					h._config["field_fkmodels"+suffix].push("");
					h._config["field_fkmodels"+suffix].push("");
					break;
				case "fk":
					var tmp = t.find_by_table(c.table,true);
					if (!tmp) throw "Can't find model for table " + c.table;
					h._config["field_fkmodels"+suffix][h._config["field_fkmodels"+suffix].length-1]={fkid:c.fkid,name:c.name,not_null:c.not_null,table:c.table,model:tmp.name};
					h._config["field_names"+suffix].push(c.name.replace(/_id$/,"_name"));
					h._config["field_datatypes"+suffix].push("");
					h._config["field_fkmodels"+suffix].push("");
					if (tmp.code_column) {
						h._config["field_names"+suffix].push(c.name.replace(/_id$/,"_code"));
						h._config["field_datatypes"+suffix].push("");
						h._config["field_fkmodels"+suffix].push("");
					}
					break;
			}
		}
		if (arr2) {
			for (var i=0;i<arr2.length;i++) {
				h._config["field_names"+suffix].push(arr2[i].name);
				h._config["field_datatypes"+suffix].push(arr2[i].datatype);
			}
		}
	}
	fill_fields(mconfig.columns,mconfig.patches.extra_fields,"");
	fill_fields(mconfig.columns_l10n,mconfig.patches.extra_fields_l10n,"_l10n");
	return JSON.stringify(h);
}

/**
 * Method: mklabel
 */
LNModelGenerator.prototype.mklabel=function(colname)
{
	return colname.substr(0,1).toUpperCase()+colname.substr(1).replace(/_id$/,"").replace(/_/g," ").replace(/([a-zA-Z])([0-9])/g,function(p,p1,p2) {return p1+" "+p2;}).replace(/([A-Z][a-z])/g,function(p,p1) {return " "+p1.toLowerCase();});
}

/**
 * Method: build_admin_view_expimp
 */
LNModelGenerator.prototype.build_admin_view_expimp=function(mconfig,mode)
{
	mconfig.label=this.mklabel(mconfig.name)+" "+mode.replace(/2/," result");
	var h={
		mconfig:mconfig
	};
	return this.views.process("system/auto_admin/"+mode,h).replace(/<\{>/g,"{{").replace(/<\}>/g,"}}");
}

/**
 * Method: build_admin_view_list
 */
LNModelGenerator.prototype.build_admin_view_list=function(mconfig)
{
	var gcolumns=[];
	mconfig.label=this.mklabel(mconfig.name)+" list";
	var h={
		mconfig:mconfig,
		grid:{
			type: "grid",
			title: "",
			name: "items",
			maxheight:null,
			rowClasses: [{
			    name:"item",
			    columns: gcolumns
			}]
		},
		fields: {},
		columns: gcolumns
	};
	var through="";
	if (mconfig.adminpanel.cols_through)
		mconfig.adminpanel.cols_through.split(/\s*,\s*/).forEach(function(c) {
			through+="&"+c+"={{I:fields."+c+"}}";
		});
	h.through=through;
	var arr;
	if (mconfig.adminpanel.list_ordering) {
	} else {
		arr=[];
		var arr2=[];
		for (var i=0;i<mconfig.columns.length;i++) if (mconfig.columns[i].name=="name") arr.push(mconfig.columns[i]); else arr2.push(mconfig.columns[i]);
		for (var i=0;i<mconfig.columns_l10n.length;i++) if (mconfig.columns_l10n[i].name=="name") arr.push(mconfig.columns_l10n[i]); else arr2.push(mconfig.columns_l10n[i]);
		arr=arr.concat(arr2);
		//mconfig.columns.slice(0);
		//if (mconfig.columns_l10n.length) arr=arr.concat(mconfig.columns_l10n.slice(0));
	}
	for (var i=0;i<arr.length;i++) {
		var c=arr[i];
		var label=this.mklabel(c.name);
		if (c.datatype=="text" || c.datatype=="clob" || c.datatype=="wysiwyg" || c.skip_admin_list || c.name=="parent_id") continue;
		var hh={name: c.name, label: "{{!"+label+"}}",out:'item.'+c.name};
		if (c.name=="name" || i==0) hh.width=200;
		if (0) hh.width=0;
		switch (c.datatype) {
			case "boolean":
				hh.type="checkbox";
				break;
			case "fk":
				hh.out='item.'+c.name.replace(/_id$/,"_name");
				break;
			case "file":
				hh.type="image";
				hh.out='item.'+c.name.replace(/_id$/,"_url");
				break;
			case "file_folder":
				break;
		}
		gcolumns.push(hh);
	}
	gcolumns.splice(1,0,
		{name:"_edit_link",label:" ",width:30,url:'{{action}}?mode=edit'+through+'&id=',html:1,out:'\'<div class="button button-edit"></div>\''},
		{name:"_delete_link",label:" ",width:30,url:'{{action}}?mode=delete'+through+'&id=',html:1,out:'\'<div class="button button-delete"></div>\''});
	if (mconfig.has_ordering) {
		h.grid.rowClasses[0].sortable=1;
		h.grid.sort={url: "<{>action<}>",params: {mode: "reorder", ajaj: 1}};
	}
	if (mconfig.has_hier) {
		
	}
	return this.views.process("system/auto_admin/index",h).replace(/<\{>/g,"{{").replace(/<\}>/g,"}}");
}

/**
 * Method: build_admin_view_editdel
 */
LNModelGenerator.prototype.build_admin_view_editdel=function(mconfig,del)
{
	var t=this;
	mconfig.label=this.mklabel(mconfig.name)+(del?" delete":" edit");
	var h={
		mconfig:mconfig,
		inputs: [
			{
				"type":"accordion","name":"blocks",mode:"blocks","label":"",
				pages:[
					{id:"main-block",name:"{{!Main information}}"}
				]
		    }
		],
		inputs_l10n: []
	};
	if (mconfig.columns_l10n.length) {
		h.inputs[0].pages.push({id:"l10n-block",name:"{{!Localized fields}}"});
		h.inputs.push({"type":"accordion","name":"l10n_tabs",mode:"tabs","label":"",pages:"<<{{JSON:l10n_pages}}>>",xpath:".l10n-block"});
	}

	function apply_columns(arr,dst,prefix,xpath)
	{
		for (var i=0;i<arr.length;i++) {
			var c=arr[i];
			var label=t.mklabel(c.name);
			if (c.skip_edit) continue;
			var hh={
				label:"{{!"+label+"}}"
			};
			if (prefix) {
				hh.name="{{l10n.id}}__"+c.name;
				hh.value='<<{{JSON:ADMINGETL("'+c.name+'"):item}}>>';
				hh.xpath=xpath;
			} else {
				hh.value="<<{{JSON:item."+c.name+"}}>>";
				hh.name=c.name;
				hh.xpath=xpath;
			}
			hh.width=700;
			switch (c.datatype) {
				case "boolean":
					hh.type="checkbox2";
					break;
				case "text":
				case "clob":
				case "wysiwyg":
					hh.type="textareaedit";
					if (c.datatype=="wysiwyg") hh.wysiwyg=1;
					break;
				case "fk":
					hh.type="select";
					hh.values="<<{{JSON:dictionaries."+c.table.toLowerCase()+"}}>>";
					//if (!c.not_null) hh.add_null=1;
					hh.add_null=1;
					if (c.not_null) hh.not_null=1;
					break;
				case "file":
					hh.type="file_id";
					hh.value={id:"<<{{JSON:item."+c.name+"}}>>",url:"<<{{JSON:item."+c.name.replace(/_id$/,"_url")+"}}>>",name:"<<{{JSON:item."+c.name.replace(/_id$/,"_name")+"}}>>"};
					break;
				case "file_folder":
					hh.type="file_group_id";
					hh.value={id:"<<{{JSON:item."+c.name+"}}>>",files:"<<{{JSON:item."+c.name.replace(/_id$/,"")+"}}>>",name:"<<{{JSON:item."+c.name.replace(/_id$/,"_name")+"}}>>"};
					break;
				case "varchar":
					hh.type="textedit";
					if (c.not_null) hh.minsize=1;
					if (c.maxsize) hh.maxsize=c.maxsize;
					if (c.maxsize<30) hh.width=14*c.maxsize+30;
					if (c.maxsize<129) hh.width=450;
					if (c.name=="code") hh.iscode=1;
					if (c.name=="password" && mconfig.name=="User") hh.type="password";
					break;
				case "date":
					hh.type="date";
					if (c.not_null) hh.not_null=1;
					hh.width=150;
					break;
				case "timestamp":
					hh.type="datetime";
					if (c.not_null) hh.not_null=1;
					hh.width=150;
					break;
				case "numeric":
				case "integer":
				case "bigint":
					hh.type="textedit";
					if (c.not_null) hh.minsize=1;
					hh.width=150;
					hh.isnumber=1;
					break;
				default:
					hh.type="textedit";
				
			}
			dst.push(hh);
		}
	}
	apply_columns(mconfig.columns		,h.inputs		,""		,".main-block");
	apply_columns(mconfig.columns_l10n	,h.inputs_l10n	,"L10N__",".l10n-{{l10n.id}}-block");
	return this.views.process("system/auto_admin/"+(del?"delete":"edit"),h).replace(/<\{>/g,"{{").replace(/<\}>/g,"}}").replace(/.<</g,"").replace(/>>./g,"").replace(/\\"/g,'"');
}

/**
 * Method: build_admin_views
 */
LNModelGenerator.prototype.build_admin_views=function(mconfig)
{
	var path=this.ssjsdir+"views/controllers/_auto_admin/AutoAdmin"+mconfig.name+"/";
	var shortpath="code/views/controllers/_auto_admin/AutoAdmin"+mconfig.name+"/";
	// LIST
	this.update_file(path+"index.html",this.build_admin_view_list(mconfig),shortpath+"index.html");
	// EDIT
	this.update_file(path+"edit.html",this.build_admin_view_editdel(mconfig,0),shortpath+"edit.html");
	// DELETE
	this.update_file(path+"delete.html",this.build_admin_view_editdel(mconfig,1),shortpath+"delete.html");

	// EXPORT IMPORT
	this.update_file(path+"export.html",this.build_admin_view_expimp(mconfig,"export"),shortpath+"export.html");
	this.update_file(path+"import.html",this.build_admin_view_expimp(mconfig,"import"),shortpath+"import.html");
//	this.update_file(path+"export2.html",this.build_admin_view_expimp(mconfig,"export2"));
	this.update_file(path+"import2.html",this.build_admin_view_expimp(mconfig,"import2"),shortpath+"import2.html");
}

/**
 * Method: build_fk_dictionary
 */
LNModelGenerator.prototype.build_fk_dictionary=function(mconfig,col)
{
	/*switch (col.table.toLowerCase()) {
		case "tusers":
			
			break;
	}*/
	var fmconfig=this.find_by_table(col.table,true);
	if (fmconfig) return {table:col.table.toLowerCase(),model:fmconfig.name};
	throw "TODO - model "+mconfig.name+", column "+col.name+", corresponding model does not exist";
}

/**
 * Method: build_fk_dictionaries
 */
LNModelGenerator.prototype.build_fk_dictionaries=function(mconfig,columns,dictionaries)
{
	for (var i=0;i<columns.length;i++) {
		var col=columns[i];
		if (col.datatype!="fk") continue;
		var a=this.build_fk_dictionary(mconfig,col);
		if (!a) continue;
		var flag=true;
		for (var j=0;j<dictionaries.length;j++) if (dictionaries[j].table==a.table) flag=false;
		if (flag) dictionaries.push(a);
	}
}

/**
 * Method: build_admin_controller
 */
LNModelGenerator.prototype.build_admin_controller=function(mconfig)
{
	var h={
		mconfig:mconfig,
		columns:[],
		fields: {},
		dictionaries:[],
		listfunc:mconfig.has_hier?"ListHier":"List",
		listname:mconfig.adminpanel.listname?mconfig.adminpanel.listname:undefined
	};
	this.build_fk_dictionaries(mconfig,mconfig.columns,h.dictionaries);
	this.build_fk_dictionaries(mconfig,mconfig.columns_l10n,h.dictionaries);
	if (mconfig.adminpanel.listhelperfunc) h.listhelper='	this.helper_footer=this.F("'+mconfig.adminpanel.listhelperfunc.replace(/\./,'","')+'","list",this.items);';
	if (mconfig.adminpanel.edithelperfunc) h.edithelper='	this.helper_footer=this.F("'+mconfig.adminpanel.edithelperfunc.replace(/\./,'","')+'","edit",this.item);';
	if (mconfig.adminpanel.savehelperfunc) h.savehelper='	this.F("'+mconfig.adminpanel.savehelperfunc.replace(/\./,'","')+'","save",item);';
	if (mconfig.adminpanel.prehelperfunc) h.prehelper='	this.F("'+mconfig.adminpanel.prehelperfunc.replace(/\./,'","')+'","pre",this.item);';
	return this.views.process("system/auto_admin/controller",h);
}



exports.LNModelGenerator=LNModelGenerator;

